﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;

namespace Dnsresolver
{
    public class DnsResolvor
    {
        private Socket dnsSocket;

        public void ResolveHost(string host, EndPoint remoteEp)
        {
            var dnsPacket = new DnsPacket();

            AddDnsPacketHeader(dnsPacket);
            AddDnsQuestion(dnsPacket, host);
            var bytesSended = 0;
            do
            {
                bytesSended += dnsSocket.SendTo(dnsPacket.PacketData, 0, dnsPacket.DataLength, SocketFlags.None, remoteEp);
            } while (bytesSended < dnsPacket.DataLength);

            dnsPacket.DataLength = dnsSocket.ReceiveFrom(dnsPacket.PacketData, ref remoteEp);
            ParseDnsPacket(dnsPacket);
        }

        private static unsafe void ParseDnsPacket(DnsPacket dnsPacket)
        {
            fixed (byte* pPacketData = dnsPacket.PacketData)
            {
                var pData = pPacketData;
                var pDnsHeader = (DnsHeader*)pData;
                var answerCount = IPAddress.NetworkToHostOrder(pDnsHeader->answerNum);
                var authorCount = IPAddress.NetworkToHostOrder(pDnsHeader->authorNum);
                var addtionnalCount = IPAddress.NetworkToHostOrder(pDnsHeader->additionNum);
                pData += sizeof(DnsHeader);
                do
                {
                    pData += (*pData) + 1;
                } while (*pData != 0);

                pData += 5;

                for (var i = 0; i < answerCount; i++)
                {
                    var pResponse = (ResponseStruct*)pData;
                    pData += sizeof(ResponseStruct);
                    var resourceLen = IPAddress.NetworkToHostOrder(pResponse->length);
                    if (resourceLen == 4)
                    {
                        var x = new byte[4];
                        x[0] = *pData;
                        x[1] = *(pData + 1);
                        x[2] = *(pData + 2);
                        x[3] = *(pData + 3);
                        var ipAddress = new IPAddress(x);
                        Console.WriteLine(ipAddress.ToString());
                    }
                    pData += resourceLen;
                }
                for (var i = 0; i < authorCount; i++)
                {
                    var pResponse = (ResponseStruct*)pData;
                    pData += sizeof(ResponseStruct);
                    var resourceLen = IPAddress.NetworkToHostOrder(pResponse->length);
                    pData += resourceLen;
                }
                for (var i = 0; i < addtionnalCount; i++)
                {
                    var pResponse = (ResponseStruct*)pData;
                    pData += sizeof(ResponseStruct);
                    var resourceLen = IPAddress.NetworkToHostOrder(pResponse->length);
                    pData += resourceLen;
                }
            }
        }

        private static unsafe void AddDnsQuestion(DnsPacket dnsPacket, string host)
        {
            var queryHeader = new QueryStruct
            {
                qclass = IPAddress.NetworkToHostOrder((short)1),
                type = IPAddress.NetworkToHostOrder((short)1)
            };

            var name = GetHostByte(host);
            dnsPacket.AllocBuff(name.Length);

            Buffer.BlockCopy(name, 0, dnsPacket.PacketData, dnsPacket.DataLength, name.Length);
            dnsPacket.DataLength += name.Length;

            var headerBytes = new byte[Marshal.SizeOf(queryHeader)];
            dnsPacket.AllocBuff(headerBytes.Length);

            fixed (byte* pHeaderBytes = headerBytes)
            {
                *((QueryStruct*)pHeaderBytes) = queryHeader;
                Buffer.BlockCopy(headerBytes, 0, dnsPacket.PacketData, dnsPacket.DataLength, headerBytes.Length);
                dnsPacket.DataLength += headerBytes.Length;
            }
        }

        private static unsafe void AddDnsPacketHeader(DnsPacket dnsPacket)
        {
            var requestHeader = new DnsHeader
            {
                answerNum = IPAddress.NetworkToHostOrder((short)0),
                questNum = IPAddress.NetworkToHostOrder((short)1),
                authorNum = IPAddress.NetworkToHostOrder((short)0),
                additionNum = IPAddress.NetworkToHostOrder((short)0),
                id = IPAddress.NetworkToHostOrder((short)0),
                flags = IPAddress.NetworkToHostOrder((short)0x0100)
            };

            var headerBytes = new byte[Marshal.SizeOf(requestHeader)];
            dnsPacket.AllocBuff(headerBytes.Length);

            fixed (byte* pHeaderBytes = headerBytes)
            {
                *((DnsHeader*)pHeaderBytes) = requestHeader;
                Buffer.BlockCopy(headerBytes, 0, dnsPacket.PacketData, 0, headerBytes.Length);
                dnsPacket.DataLength += headerBytes.Length;
            }
        }

        private static byte[] GetHostByte(string host)
        {
            var newHost = string.Format(".{0}.", host);
            var hostNameBytes = new byte[newHost.Length];
            for (var i = 0; i < newHost.Length; i++)
            {
                if (newHost[i] == '.')
                {
                    if (i + 1 == newHost.Length)
                    {
                        hostNameBytes[i] = 0;
                        break;
                    }
                    else
                    {
                        var nextDotPos = newHost.IndexOf('.', i + 1);
                        hostNameBytes[i] = (byte)(nextDotPos - i - 1);
                    }
                }
                else
                {
                    hostNameBytes[i] = (byte)newHost[i];
                }
            }
            return hostNameBytes;

        }

        public void StartService()
        {
            dnsSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            var hostEntry = Dns.GetHostEntry(Dns.GetHostName());
            var endPoint = new IPEndPoint(hostEntry.AddressList[0], 0);
            dnsSocket.Bind(endPoint);
        }

        public void ShutDownService()
        {
            dnsSocket.Shutdown(SocketShutdown.Both);
            dnsSocket.Close();
        }
    }
}
